"""
钉钉认证工具 - 处理钉钉OAuth2认证流程
"""

# 导入必要的标准库
import urllib.parse  # 用于URL编码
import httpx  # 异步HTTP客户端
import secrets  # 用于生成安全的随机数
from typing import Tuple, Optional  # 用于类型提示

# 导入项目内部模块
from config import settings  # 应用配置


def generate_dingtalk_auth_url() -> Tuple[str, str]:
    """
    生成钉钉授权URL
    
    返回:
        包含授权URL和state的元组
    """
    # 生成随机的state参数，用于防止CSRF攻击
    state = secrets.token_urlsafe(32)
    
    # 构造钉钉OAuth2授权URL的参数
    params = {
        'client_id': settings.DINGTALK_APP_KEY,  # 应用的AppKey
        'redirect_uri': settings.DINGTALK_REDIRECT_URI,  # 回调URL
        'response_type': 'code',  # 响应类型，固定为code
        'scope': 'openid corpid',  # 请求的权限范围
        'state': state  # 防CSRF攻击的state参数
    }
    
    # 将参数编码为URL查询字符串
    query_string = urllib.parse.urlencode(params)
    
    # 构造完整的授权URL
    auth_url = f"https://login.dingtalk.com/oauth2/auth?{query_string}"
    
    # 返回授权URL和state
    return auth_url, state


class DingtalkClient:
    """
    钉钉API客户端
    用于调用钉钉的各种API接口
    """
    
    def __init__(self, client_id: str, client_secret: str):
        """
        初始化钉钉客户端
        
        参数:
            client_id: 钉钉应用的AppKey
            client_secret: 钉钉应用的AppSecret
        """
        # 设置钉钉API的基础URL
        self.base_url = "https://api.dingtalk.com"
        
        # 设置应用凭证
        self.client_id = client_id
        self.client_secret = client_secret

    async def get_access_token(self, code: str) -> Optional[dict]:
        """
        通过授权码获取用户访问令牌
        
        参数:
            code: 钉钉授权码
            
        返回:
            包含访问令牌等信息的字典，如果失败则返回None
        """
        try:
            # 构造获取访问令牌的API URL
            url = f"{self.base_url}/v1.0/oauth2/userAccessToken"
            
            # 构造请求数据
            data = {
                "clientId": self.client_id,      # 应用的AppKey
                "clientSecret": self.client_secret,  # 应用的AppSecret
                "code": code,                    # 授权码
                "grantType": "authorization_code"   # 授权类型
            }
            
            # 打印调试信息
            print(f"[DEBUG] 请求URL: {url}")
            print(f"[DEBUG] 请求参数: clientId={self.client_id}, code={code[:20]}...")

            # 使用httpx异步客户端发送POST请求
            async with httpx.AsyncClient(timeout=10.0) as client:
                response = await client.post(url, json=data)

                # 检查响应状态码
                if response.status_code == 200:
                    # 解析响应JSON
                    resp_data = response.json()
                    
                    # 检查是否成功获取访问令牌
                    if "accessToken" in resp_data:
                        print(f"[成功] 成功获取 accessToken")
                        print(f"返回数据: {resp_data}")
                        return resp_data
                    else:
                        print(f"[错误] 获取token失败: {resp_data}")
                        return None
                else:
                    print(f"[错误] HTTP错误: {response.status_code}")
                    print(f"响应内容: {response.text}")
                    return None
        except Exception as e:
            # 捕获并打印异常
            print(f"[错误] 获取Access Token异常: {e}")
            return None

    async def _get_user_info(self, access_token: str) -> Optional[dict]:
        """
        通过访问令牌获取用户信息（内部方法）
        
        参数:
            access_token: 用户访问令牌
            
        返回:
            包含用户信息的字典，如果失败则返回None
        """
        try:
            # 构造获取用户信息的API URL
            url = f"{self.base_url}/v1.0/contact/users/me"
            
            # 构造请求头
            headers = {
                "x-acs-dingtalk-access-token": access_token  # 在请求头中携带访问令牌
            }
            
            # 打印调试信息
            print(f"[DEBUG] 请求用户信息URL: {url}")

            # 使用httpx异步客户端发送GET请求
            async with httpx.AsyncClient(timeout=10.0) as client:
                response = await client.get(url, headers=headers)

                # 检查响应状态码
                if response.status_code == 200:
                    # 解析响应JSON
                    user_info = response.json()
                    print(f"[成功] 获取用户信息: {user_info}")
                    return user_info
                else:
                    print(f"[错误] 获取用户信息失败: {response.status_code}")
                    print(f"响应内容: {response.text}")
                    return None
        except Exception as e:
            # 捕获并打印异常
            print(f"[错误] 获取用户信息异常: {e}")
            return None

    async def get_user_info_by_code(self, code: str) -> Optional[dict]:
        """
        通过授权码直接获取用户信息
        
        这是对外提供的主要方法，它会先通过授权码获取访问令牌，
        然后使用访问令牌获取用户信息
        
        参数:
            code: 钉钉授权码
            
        返回:
            包含用户信息的字典，如果失败则返回None
        """
        # 第一步：通过授权码获取访问令牌
        token_info = await self.get_access_token(code)
        if not token_info:
            return None

        # 从令牌信息中提取访问令牌
        access_token = token_info.get("accessToken")
        if not access_token:
            return None

        # 第二步：通过访问令牌获取用户信息
        user_info = await self._get_user_info(access_token)
        return user_info


# 创建钉钉客户端实例
# 使用配置中的AppKey和AppSecret初始化
dingtalk_client = DingtalkClient(settings.DINGTALK_APP_KEY, settings.DINGTALK_APP_SECRET)